production.
=== Configuration changes in 1.23 ===
+ * Introduced $wgPagePropsHaveSortkey as a backwards-compatibility switch,
+ for using the old schema of the page_props table, in case the respective
+ schema update was not applied.
* When $wgJobRunRate is higher that zero, jobs are now executed via an
asynchronous HTTP request to a MediaWiki entry point. This may require
- increasing the number of server worker threads.
+ increasing the number of server worker threads. $wgRunJobsAsync has been
+ added to disable this feature if needed, falling back to executing the job
+ on the same process but making the execution synchronously.
* $wgDebugLogGroups values may be set to an associative array with a
'destination' key specifying the log destination. The array may also contain
a 'sample' key with a positive integer value N indicating that the log group
wgQueryPages hook.
* $wgHttpOnlyBlacklist has been removed.
* $wgLicenseTerms has been removed as it was unused.
+* $wgProfileOnly is now deprecated; set the log file in
+ $wgDebugLogGroups['profileoutput'] to replace it.
+* $wgMaxBacklinksInvalidate was removed; use $wgJobBackoffThrottling instead
+* Deprecated ResourceLoaderGetStartupModules hook.
=== New features in 1.23 ===
* ResourceLoader can utilize the Web Storage API to cache modules client-side.
installer has been updated to use it.
* Changes to content typography (fonts, line-height, etc.). See
https://www.mediawiki.org/wiki/Typography_refresh for further information.
+* The Vector skin's visual treatment of external links has been simplified to a
+ single icon (from nine). This should not affect local rules unless they were
+ re-using these icons, which have now been deleted.
* ResourceLoader: mw.loader.using() now implements a Promise interface.
+* Add new hook ChangesListInitRows accessed via ChangesList::initChangesListRows.
+ If called by the ChangesList consumer this gives extensions a chance to batch
+ process the result set prior to rendering.
+* A PoolCounterRedis class was added which can be make use of in $wgPoolCounterConf.
+ This requires at least one Redis 2.6+ server.
+* $wgProfileToDatabase was removed. Set $wgProfiler to ProfilerSimpleDB
+ in StartProfiler.php instead of using this.
+* (bug 63444) Made it possible to change the indent string (default: 4 spaces)
+ used by FormatJson::encode().
=== Bug fixes in 1.23 ===
* (bug 41759) The "updated since last visit" markers (on history pages, recent
* (bug 42026) Deprecated uctoponly in favor of ucshow=top.
* list=search no longer has a "srredirects" parameter. Redirects are now
included in all searches.
+* Added list=prefixsearch that works like action=opensearch but can be used as
+ a generator.
+* (bug 24782) Various modules will now use unique continuation parameters.
+* (bug 63249) Cache RecentChanges Atom feed in varnish for 15 seconds.
=== Languages updated in 1.23 ===
* Support was added for Northern Luri (lrc).
=== Other changes in 1.23 ===
+ * Added pp_sortkey column to page_props table, so pages can be efficiently
+ queried and sorted by property value (bug 58032).
+ See $wgPagePropsHaveSortkey if you want to postpone the schema change.
* The rc_type field in the recentchanges table has been superseded by a new
rc_source field. The rc_source field is a string representation of the
change type where rc_type was a numeric constant. This field is not yet
* Special:Search no longer has an "include redirects" option on the advanced
tab. Redirects are now included in all searches.
* mediawiki.api.category's getCategories() 'async' parameter was deprecated.
+* The locations of resources have been split between upstream libraries, now in
+ resources/lib/, local libaries in resources/src/, and local forks of upstream
+ libraries, also in resources/src/.
+* BREAKING CHANGE: The automatically-generated function closure with which
+ ResourceLoader wraps all modules' JavaScript code now binds the identifier
+ names 'jQuery' and '$' to the jQuery object of the version of jQuery that is
+ bundled with MediaWiki. If you bind these names to other objects in global
+ scope (like Zepto.js or document.querySelectorAll, for example) you will need
+ to use different names to or re-bind them at the top of each
+ ResourceLoader-loaded module.
+* (bug 52342) Preference "Remember my login" was removed.
==== Removed classes ====
* FakeMemCachedClient (deprecated in 1.18)
* MediaWiki version number
* @since 1.2
*/
-$wgVersion = '1.23alpha';
+$wgVersion = '1.24alpha';
/**
* Name of the site. It must be changed in LocalSettings.php
*/
$wgImgAuthDetails = false;
-/**
- * If this is enabled, img_auth.php will not allow image access unless the wiki
- * is private. This improves security when image uploads are hosted on a
- * separate domain.
- */
-$wgImgAuthPublicTest = true;
-
/**
* Map of relative URL directories to match to internal mwstore:// base storage paths.
* For img_auth.php requests, everything after "img_auth.php/" is checked to see
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
*
- * @deprecated In new code, use the $wiki parameter to wfGetLB() to access
- * remote databases. Using wfGetLB() allows the shared database to reside on
- * separate servers to the wiki's own database, with suitable configuration
- * of $wgLBFactoryConf.
+ * @deprecated since 1.21 In new code, use the $wiki parameter to wfGetLB() to
+ * access remote databases. Using wfGetLB() allows the shared database to
+ * reside on separate servers to the wiki's own database, with suitable
+ * configuration of $wgLBFactoryConf.
*/
$wgSharedDB = null;
* @since 1.22
*/
$wgResourceLoaderLESSImportPaths = array(
- "$IP/resources/mediawiki.less/",
+ "$IP/resources/src/mediawiki.less/",
"$IP/skins/vector/",
);
'previewontop' => 1,
'rcdays' => 7,
'rclimit' => 50,
- 'rememberpassword' => 0,
'rows' => 25,
'showhiddencats' => 0,
'shownumberswatching' => 1,
'ip' => null,
'subnet' => null,
),
+ 'renderfile-nonstandard' => array( // same as above but for non-standard thumbnails
+ 'anon' => null,
+ 'user' => null,
+ 'newbie' => null,
+ 'ip' => null,
+ 'subnet' => null,
+ ),
);
/**
/**
* Don't put non-profiling info into log file
- */
-$wgProfileOnly = false;
-
-/**
- * Log sums from profiling into "profiling" table in db.
- *
- * You have to create a 'profiling' table in your database before using
- * this feature. Run set $wgProfileToDatabase to true in
- * LocalSettings.php and run maintenance/update.php or otherwise
- * manually add patch-profiling.sql to your database.
*
- * To enable profiling, edit StartProfiler.php
+ * @deprecated since 1.23, set the log file in
+ * $wgDebugLogGroups['profileoutput'] instead.
*/
-$wgProfileToDatabase = false;
+$wgProfileOnly = false;
/**
* If true, print a raw call tree instead of per-function report
* * 'formatter' -- the class name (implementing RCFeedFormatter) which will
* produce the text to send.
* * 'omit_bots' -- whether the bot edits should be in the feed
+ * * 'omit_anon' -- whether anonymous edits should be in the feed
+ * * 'omit_user' -- whether edits by registered users should be in the feed
+ * * 'omit_minor' -- whether minor edits should be in the feed
+ * * 'omit_patrolled' -- whether patrolled edits should be in the feed
* The IRC-specific options are:
* * 'add_interwiki_prefix' -- whether the titles should be prefixed with
* the first entry in the $wgLocalInterwikis array (or the value of
*
* Since MediaWiki 1.23, use of this variable to define messages is discouraged; instead, store
* messages in JSON format and use $wgMessagesDirs. For setting other variables than
- * $messages, $wgExtensionMessagesFiles should still be used.
+ * $messages, $wgExtensionMessagesFiles should still be used. Use a DIFFERENT key because
+ * any entry having a key that also exists in $wgMessagesDirs will be ignored.
*
- * If there is an entry in $wgMessagesDirs with the same key as one in
- * $wgExtensionMessagesFiles, then any $messages variables set in the $wgExtensionMessagesFiles file
- * will be ignored. This means an extension that only provides messages can be backwards compatible
- * by using both $wgExtensionMessagesFiles and $wgMessagesDirs, and only one of the two
- * will be used depending on what the version of MediaWiki supports.
+ * Extensions using the JSON message format can preserve backward compatibility with
+ * earlier versions of MediaWiki by using a compatibility shim, such as one generated
+ * by the generateJsonI18n.php maintenance script, listing it under the SAME key
+ * as for the $wgMessagesDirs entry.
*
* @par Example:
* @code
* @since 1.23
*/
$wgMessagesDirs = array(
- 'oojs-ui' => "$IP/resources/oojs-ui/i18n",
+ 'core' => "$IP/languages/i18n",
+ 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
);
/**
* on each job runner process. The meaning of "work items" varies per job,
* but typically would be something like "pages to update". A single job
* may have a variable number of work items, as is the case with batch jobs.
+ * This is used by runJobs.php and not jobs run via $wgJobRunRate.
* These settings should be global to all wikis.
*/
$wgJobBackoffThrottling = array();
*/
$wgJobRunRate = 1;
+/**
+ * When $wgJobRunRate > 0, try to run jobs asynchronously, spawning a new process
+ * to handle the job execution, instead of blocking the request until the job
+ * execution finishes.
+ * @since 1.23
+ */
+$wgRunJobsAsync = true;
+
/**
* Number of rows to update per job
*/
*/
$wgUpdateRowsPerQuery = 100;
-/**
- * Do not purge all the pages that use a page when it is edited
- * if there are more than this many such pages. This is used to
- * avoid invalidating a large portion of the squid/parser cache.
- *
- * This setting should factor in any squid/parser cache expiry settings.
- */
-$wgMaxBacklinksInvalidate = false;
-
/** @} */ # End job queue }
/************************************************************************//**
*/
$wgCompiledFiles = array();
+ /**
+ * Whether the page_props table has a pp_sortkey column. Set to false in case
+ * the respective database schema change was not applied.
+ * @since 1.23
+ */
+ $wgPagePropsHaveSortkey = true;
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
}
/**
- * @param $cats
+ * @param array $cats
*/
function invalidateCategories( $cats ) {
$this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
}
/**
- * @param $images
+ * @param array $images
*/
function invalidateImageDescriptions( $images ) {
$this->invalidatePages( NS_FILE, array_keys( $images ) );
$toField = $prefix . '_to';
}
if ( count( $deletions ) ) {
- $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
+ $where[$toField] = array_keys( $deletions );
} else {
$where = false;
}
/**
* Get an array of category insertions
*
- * @param array $existing mapping existing category names to sort keys. If both
+ * @param array $existing Mapping existing category names to sort keys. If both
* match a link in $this, the link will be omitted from the output
*
* @return array
/**
* Get an array of interlanguage link insertions
*
- * @param array $existing mapping existing language codes to titles
+ * @param array $existing Mapping existing language codes to titles
*
* @return array
*/
*/
function getPropertyInsertions( $existing = array() ) {
$diffs = array_diff_assoc( $this->mProperties, $existing );
+
$arr = array();
- foreach ( $diffs as $name => $value ) {
- $arr[] = array(
- 'pp_page' => $this->mId,
- 'pp_propname' => $name,
- 'pp_value' => $value,
- );
+ foreach ( array_keys( $diffs ) as $name ) {
+ $arr[] = $this->getPagePropRowData( $name );
}
return $arr;
}
+ /**
+ * Returns an associative array to be used for inserting a row into
+ * the page_props table. Besides the given property name, this will
+ * include the page id from $this->mId and any property value from
+ * $this->mProperties.
+ *
+ * The array returned will include the pp_sortkey field if this
+ * is present in the database (as indicated by $wgPagePropsHaveSortkey).
+ * The sortkey value is currently determined by getPropertySortKeyValue().
+ *
+ * @note: this assumes that $this->mProperties[$prop] is defined.
+ *
+ * @param string $prop The name of the property.
+ *
+ * @return array
+ */
+ private function getPagePropRowData( $prop ) {
+ global $wgPagePropsHaveSortkey;
+
+ $value = $this->mProperties[$prop];
+
+ $row = array(
+ 'pp_page' => $this->mId,
+ 'pp_propname' => $prop,
+ 'pp_value' => $value,
+ );
+
+ if ( $wgPagePropsHaveSortkey ) {
+ $row['pp_sortkey'] = $this->getPropertySortKeyValue( $value );
+ }
+
+ return $row;
+ }
+
+ /**
+ * Determines the sort key for the given property value.
+ * This will return $value if it is a float or int,
+ * 1 or resp. 0 if it is a bool, and null otherwise.
+ *
+ * @note: In the future, we may allow the sortkey to be specified explicitly
+ * in ParserOutput::setProperty.
+ *
+ * @param mixed $value
+ *
+ * @return float|null
+ */
+ private function getPropertySortKeyValue( $value ) {
+ if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
+ return floatval( $value );
+ }
+
+ return null;
+ }
+
/**
* Get an array of interwiki insertions for passing to the DB
* Skips the titles specified by the 2-D array $existing
/**
* Get an array of existing categories, with the name in the key and sort key in the value.
*
- * @return array of property names and values
+ * @return array Array of property names and values
*/
private function getExistingProperties() {
$res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
/**
* Fetch page links added by this LinksUpdate. Only available after the update is complete.
* @since 1.22
- * @return null|array of Titles
+ * @return null|array Array of Titles
*/
public function getAddedLinks() {
if ( $this->linkInsertions === null ) {
/**
* Fetch page links removed by this LinksUpdate. Only available after the update is complete.
* @since 1.22
- * @return null|array of Titles
+ * @return null|array Array of Titles
*/
public function getRemovedLinks() {
if ( $this->linkDeletions === null ) {
array( 'changeNullableField', 'image', 'img_metadata', 'NOT NULL' ),
array( 'changeNullableField', 'filearchive', 'fa_metadata', 'NOT NULL' ),
array( 'changeNullableField', 'recentchanges', 'rc_cur_id', 'NULL' ),
+ array( 'changeNullableField', 'recentchanges', 'rc_cur_time', 'NULL' ),
array( 'checkOiDeleted' ),
array( 'addPgField', 'recentchanges', 'rc_source', "TEXT NOT NULL DEFAULT ''" ),
array( 'addPgField', 'page', 'page_links_updated', "TIMESTAMPTZ NULL" ),
array( 'addPgField', 'mwuser', 'user_password_expires', 'TIMESTAMPTZ NULL' ),
+ array( 'addPgField', 'page_props', 'pp_sortkey', 'float NULL' ),
+ array( 'addPgIndex', 'page_props', 'pp_propname_sortkey_page',
+ '( pp_propname, pp_sortkey, pp_page ) WHERE ( pp_sortkey NOT NULL )' ),
);
}
/**
* callback used by getText to replace editsection tokens
* @private
- * @param $m
+ * @param array $m
* @throws MWException
* @return mixed
*/
/**
* Checks, if a url is pointing to the own server
*
- * @param string $internal the server to check against
- * @param string $url the url to check
+ * @param string $internal The server to check against
+ * @param string $url The url to check
* @return bool
*/
static function isLinkInternal( $internal, $url ) {
/**
* Record a local or interwiki inline link for saving in future link tables.
*
- * @param $title Title object
- * @param $id Mixed: optional known page_id so we can skip the lookup
+ * @param Title $title
+ * @param int|null $id Optional known page_id so we can skip the lookup
*/
function addLink( Title $title, $id = null ) {
if ( $title->isExternal() ) {
* Register a file dependency for this output
* @param string $name Title dbKey
* @param string $timestamp MW timestamp of file creation (or false if non-existing)
- * @param string $sha1 base 36 SHA-1 of file (or false if non-existing)
+ * @param string $sha1 Base 36 SHA-1 of file (or false if non-existing)
* @return void
*/
function addImage( $name, $timestamp = null, $sha1 = null ) {
/**
* Register a template dependency for this output
- * @param $title Title
- * @param $page_id
- * @param $rev_id
+ * @param Title $title
+ * @param int $page_id
+ * @param int $rev_id
* @return void
*/
function addTemplate( $title, $page_id, $rev_id ) {
}
/**
- * @param $title Title object, must be an interwiki link
+ * @param Title $title Title object, must be an interwiki link
* @throws MWException if given invalid input
*/
function addInterwikiLink( $title ) {
* Add some text to the "<head>".
* If $tag is set, the section with that tag will only be included once
* in a given page.
+ * @param string $section
+ * @param string|bool $tag
*/
function addHeadItem( $section, $tag = false ) {
if ( $tag !== false ) {
/**
* Add one or more variables to be set in mw.config in JavaScript.
*
- * @param $keys {String|Array} Key or array of key/value pairs.
- * @param $value {Mixed} [optional] Value of the configuration variable.
+ * @param string|array $keys Key or array of key/value pairs.
+ * @param mixed $value [optional] Value of the configuration variable.
* @since 1.23
*/
public function addJsConfigVars( $keys, $value = null ) {
/**
* Copy items from the OutputPage object into this one
*
- * @param $out OutputPage object
+ * @param OutputPage $out
*/
public function addOutputPageMetadata( OutputPage $out ) {
$this->addModules( $out->getModules() );
/**
* Get the title to be used for display
*
- * @return String
+ * @return string
*/
public function getDisplayTitle() {
$t = $this->getTitleText();
* retrieved given the page ID or via a DB join when given the page
* title.
*
+ * Since 1.23, page_props are also indexed by numeric value, to allow
+ * for efficient "top k" queries of pages wrt a given property.
+ *
* setProperty() is thus used to propagate properties from the parsed
* page to request contexts other than a page view of the currently parsed
* article.
/**
* Returns the options from its ParserOptions which have been taken
* into account to produce this output or false if not available.
- * @return mixed Array
+ * @return array
*/
public function getUsedOptions() {
if ( !isset( $this->mAccessedOptions ) ) {
* @see ParserCache::save
* @see ParserOptions::addExtraKey
* @see ParserOptions::optionsHash
+ * @param string $option
*/
public function recordOption( $option ) {
$this->mAccessedOptions[$option] = true;
*
* @since 1.20
*
- * @param $title Title The title of the page we're updating. If not given, a title object will be created
- * based on $this->getTitleText()
- * @param $recursive Boolean: queue jobs for recursive updates?
+ * @param Title $title The title of the page we're updating. If not given, a title object will
+ * be created based on $this->getTitleText()
+ * @param bool $recursive Queue jobs for recursive updates?
*
- * @return Array. An array of instances of DataUpdate
+ * @return array An array of instances of DataUpdate
*/
public function getSecondaryDataUpdates( Title $title = null, $recursive = true ) {
if ( is_null( $title ) ) {
* @since 1.21
*
* @param string $key The key for accessing the data. Extensions should take care to avoid
- * conflicts in naming keys. It is suggested to use the extension's name as a
- * prefix.
+ * conflicts in naming keys. It is suggested to use the extension's name as a prefix.
*
* @param mixed $value The value to set. Setting a value to null is equivalent to removing
- * the value.
+ * the value.
*/
public function setExtensionData( $key, $value ) {
if ( $value === null ) {
$po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored
$po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored
- $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array(
- array( NS_MAIN, 'Foo' ),
- ) );
+ $update = $this->assertLinksUpdate(
+ $t,
+ $po,
+ 'pagelinks',
+ 'pl_namespace,
+ pl_title',
+ 'pl_from = 111',
+ array( array( NS_MAIN, 'Foo' ) )
+ );
$this->assertArrayEquals( array(
Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state....
), $update->getAddedLinks() );
$po->addLink( Title::newFromText( "Bar" ) );
$po->addLink( Title::newFromText( "Talk:Bar" ) );
- $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array(
- array( NS_MAIN, 'Bar' ),
- array( NS_TALK, 'Bar' ),
- ) );
+ $update = $this->assertLinksUpdate(
+ $t,
+ $po,
+ 'pagelinks',
+ 'pl_namespace,
+ pl_title',
+ 'pl_from = 111',
+ array(
+ array( NS_MAIN, 'Bar' ),
+ array( NS_TALK, 'Bar' ),
+ )
+ );
$this->assertArrayEquals( array(
Title::makeTitle( NS_MAIN, 'Bar' ),
Title::makeTitle( NS_TALK, 'Bar' ),
$po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 );
- $this->assertLinksUpdate( $t, $po, 'templatelinks', 'tl_namespace, tl_title', 'tl_from = 111', array(
- array( NS_TEMPLATE, 'Foo' ),
- ) );
+ $this->assertLinksUpdate(
+ $t,
+ $po,
+ 'templatelinks',
+ 'tl_namespace,
+ tl_title',
+ 'tl_from = 111',
+ array( array( NS_TEMPLATE, 'Foo' ) )
+ );
}
/**
* @covers ParserOutput::setProperty
*/
public function testUpdate_page_props() {
+ global $wgPagePropsHaveSortkey;
+
/** @var ParserOutput $po */
list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
- $po->setProperty( "foo", "bar" );
+ $fields = array( 'pp_propname', 'pp_value' );
+ $expected = array();
- $this->assertLinksUpdate( $t, $po, 'page_props', 'pp_propname, pp_value', 'pp_page = 111', array(
- array( 'foo', 'bar' ),
- ) );
+ $po->setProperty( "bool", true );
+ $expected[] = array( "bool", true );
+
+ $po->setProperty( "float", 4.0 + 1.0/4.0 );
+ $expected[] = array( "float", 4.0 + 1.0/4.0 );
+
+ $po->setProperty( "int", -7 );
+ $expected[] = array( "int", -7 );
+
+ $po->setProperty( "string", "33 bar" );
+ $expected[] = array( "string", "33 bar" );
+
+ // compute expected sortkey values
+ if ( $wgPagePropsHaveSortkey ) {
+ $fields[] = 'pp_sortkey';
+
+ foreach ( $expected as &$row ) {
+ $value = $row[1];
+
+ if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
+ $row[] = floatval( $value );
+ } else {
+ $row[] = null;
+ }
+ }
+ }
+
+ $this->assertLinksUpdate( $t, $po, 'page_props', $fields, 'pp_page = 111', $expected );
+ }
+
+ public function testUpdate_page_props_without_sortkey() {
+ $this->setMwGlobals( 'wgPagePropsHaveSortkey', false );
+
+ $this->testUpdate_page_props();
}
// @todo test recursive, too!
- protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) {
+ protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput,
+ $table, $fields, $condition, array $expectedRows
+ ) {
$update = new LinksUpdate( $title, $parserOutput );
//NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction.